package com.zzedu.framework.aspectj;

import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.zzedu.common.annotation.Log;
import com.zzedu.common.core.domain.entity.SysUser;
import com.zzedu.common.enums.BusinessStatus;
import com.zzedu.common.enums.OnlineStatus;
import com.zzedu.common.utils.AddressUtils;
import com.zzedu.common.utils.IpUtils;
import com.zzedu.common.utils.SecurityUtils;
import com.zzedu.common.utils.ServletUtils;
import com.zzedu.common.utils.security.LoginUser;
import com.zzedu.common.utils.uuid.IdUtils;
import com.zzedu.framework.manager.factory.AsyncFactory;
import com.zzedu.system.domain.SysOperationLog;
import com.zzedu.system.domain.SysUserOnline;
import com.zzedu.system.service.ISysUserOnlineService;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.session.Session;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

/**
 * 请求拦截 #保存日志
 */
@Aspect
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class RequestLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);

    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
    @Value("${server.port}")
    private Integer port;

    @Autowired
    ISysUserOnlineService sysUserOnlineService;

    @Autowired
    RedisSessionRepository redisIndexedSessionRepository;

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.RequestMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.PostMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.DeleteMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.PutMapping)")
    public void webPointCut() {
    }

    @Around("webPointCut())")
    public Object aroundWeb(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch watch = new StopWatch();
        watch.start();

        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (attrs != null) ? attrs.getRequest() : null;

        MDC.put("traceId", IdUtils.fastSimpleUUID());
        if (request != null) {
            MDC.put("ip", IpUtils.getIpAddr(request));
        }
        SysOperationLog operLog = new SysOperationLog();
        try {
            // 基础日志记录
            if (request != null) {
                logger.info("请求IP: {}", IpUtils.getIpAddr(request));
                logger.info("请求地址: {}", request.getRequestURL());
                logger.info("HTTP METHOD: {}", request.getMethod());
                logger.info("调用方法: {}.{}", pjp.getSignature().getDeclaringTypeName(), pjp.getSignature().getName());
                if (null != pjp.getArgs()) {
                    Object[] args = Arrays.stream(pjp.getArgs())
                            .filter(arg -> !isFilterObject(arg))
                            .toArray();
                    logger.info("请求参数: {}", JSON.toJSONString(args));
                }
            }

            // 执行目标方法
            Object result = pjp.proceed();
            watch.stop();
            long costTime = watch.getTotalTimeMillis();
            // 记录耗时到 request scope，供其他地方使用（可选）
            if (attrs != null) {
                attrs.setAttribute("request_cost_time", costTime, RequestAttributes.SCOPE_REQUEST);
            }
            logger.info("返回值: {}", JSON.toJSONString(result));
            logger.info("耗时: {} ms", costTime);
            // 构建并记录操作日志
            buildAndRecordLog(pjp, request, operLog, result, null, costTime);
            return result;
        } catch (Exception e) {
            watch.stop();
            long costTime = watch.getTotalTimeMillis();
            // 构建并记录异常日志
            buildAndRecordLog(pjp, request, operLog, null, e, costTime);
            throw e; // 抛出原异常
        } finally {
            MDC.clear();
            synDb(ServletUtils.getRequest());
        }
    }

    private void synDb(HttpServletRequest request) {
        try {
            Session session = redisIndexedSessionRepository.findById(request.getRequestedSessionId());
            if (null == session) {
                return;
            }
            LoginUser userSecurity = SecurityUtils.getLoginUser();
            if (null == userSecurity || null == userSecurity.getUser()) {
                return;
            }
            SysUser loginUser = userSecurity.getUser();
            if (null == loginUser) {
                return;
            }
            SysUserOnline sysUserOnline = new SysUserOnline();
            ZoneId zoneId = ZoneId.of("Asia/Shanghai");
            sysUserOnline.setStartTimestamp(LocalDateTime.ofInstant(session.getCreationTime(), zoneId));
            sysUserOnline.setLastAccessTime(LocalDateTime.ofInstant(session.getLastAccessedTime(), zoneId));
            sysUserOnline.setSessionId(session.getId());
            UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
            // 获取客户端操作系统
            String os = userAgent.getOperatingSystem().getName();
            // 获取客户端浏览器
            String browser = userAgent.getBrowser().getName();
            sysUserOnline.setIpaddr(IpUtils.getIpAddr(request));
            sysUserOnline.setBrowser(browser);
            sysUserOnline.setOs(os);
            sysUserOnline.setLoginLocation(AddressUtils.getRealAddressByIP(sysUserOnline.getIpaddr()));
            sysUserOnline.setLoginName(loginUser.getUserName());
            if (null != loginUser.getDept()) {
                sysUserOnline.setDeptName(loginUser.getDept().getDeptName());
            }
            sysUserOnline.setUserType(String.valueOf(loginUser.getUserType()));
            sysUserOnline.setStatus(OnlineStatus.on_line.name());
            sysUserOnline.setUserId(loginUser.getId());
            sysUserOnline.setExpireTime(LocalDateTime.ofInstant(session.getLastAccessedTime().plus(session.getMaxInactiveInterval()), zoneId));
            sysUserOnlineService.saveOnline(sysUserOnline);
        } catch (Exception e) {

        }
    }

    private void buildAndRecordLog(ProceedingJoinPoint pjp,
                                   HttpServletRequest request, SysOperationLog operLog,
                                   Object jsonResult, Exception e, long costTime) {
        try {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            Log logAnnotation = AnnotationUtils.findAnnotation(method, Log.class);
            if (null == logAnnotation) {
                return;
            }
            SysUser currentUser = SecurityUtils.getSysUser();

            operLog.setStatus(e == null ? BusinessStatus.SUCCESS.ordinal() : BusinessStatus.FAIL.ordinal());
            operLog.setOperTime(LocalDateTime.now());

            if (request != null) {
                operLog.setOperIp(IpUtils.getIpAddr(request));
                operLog.setOperUrl(IpUtils.getLocalIpAddress() + ":" + port + request.getRequestURI());
                operLog.setRequestMethod(request.getMethod());
            }

            if (currentUser != null) {
                operLog.setOperName(currentUser.getUserName());
                operLog.setDeptName(currentUser.getDept() != null ? currentUser.getDept().getDeptName() : null);
                operLog.setCreateBy(currentUser.getId());
                operLog.setUserType(currentUser.getUserType());
            }

            if (e != null) {
                JSONObject errorObj = new JSONObject();
                errorObj.put("msg", getStackTrace(e));
                operLog.setErrorMsg(errorObj.toJSONString());
            }
            operLog.setMethod(pjp.getTarget().getClass().getName() + "#" + method.getName());
            operLog.setCostTime(costTime); // 直接传入耗时

            fillAnnotationInfo(pjp, logAnnotation, operLog, jsonResult);
            AsyncFactory.recordOper(operLog);
        } catch (Exception ex) {
            logger.error("记录操作日志异常: ", ex);
        }
    }

    private void fillAnnotationInfo(ProceedingJoinPoint pjp, Log log, SysOperationLog operLog, Object jsonResult) {
        operLog.setBusinessType(log.businessType().ordinal());
        operLog.setTitle(log.title());
        operLog.setOperatorType(log.operatorType().ordinal());

        if (log.isSaveRequestData()) {
            recordRequestData(pjp, operLog);
        }

        if (log.isSaveResponseData() && jsonResult != null) {
            JSONWriter.Feature[] writerFeatures = new JSONWriter.Feature[]{
                    JSONWriter.Feature.WriteEnumsUsingName
            };
            operLog.setJsonResult(JSON.toJSONString(jsonResult, excludePropertyPreFilter(), writerFeatures));
        }
    }

    private void recordRequestData(ProceedingJoinPoint pjp, SysOperationLog operLog) {
        // 过滤掉不希望记录的对象
        Object[] args = Arrays.stream(pjp.getArgs())
                .filter(arg -> !isFilterObject(arg))
                .toArray();
        operLog.setOperParam(JSON.toJSONString(args, excludePropertyPreFilter()));
    }

    public PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter() {
        return new PropertyPreFilters().addFilter().addExcludes(EXCLUDE_PROPERTIES);
    }

    public static String getStackTrace(Throwable throwable) {
        StringBuilder sb = new StringBuilder();
        sb.append(throwable.toString()).append("\n");
        for (StackTraceElement element : throwable.getStackTrace()) {
            sb.append("\tat ").append(element.toString()).append("\n");
        }
        return sb.toString();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        if (null == o) {
            return true;
        }
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}